package com.tengyun.middleplatform.trade.etl.config;

import com.tengyun.middleplatform.trade.etl.exception.ArgumentErrorInfoEnum;
import com.tengyun.middleplatform.trade.etl.exception.CommonErrorInfoEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import javax.validation.ConstraintViolationException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashSet;
import java.util.Set;

/**
 * @Author: likailun
 * @Description: 全局异常处理器
 * @Date: create in 2022/3/7 2:41 下午
 */
@Slf4j
@Component
@ControllerAdvice
public class GlobalExceptionHandler {

    /**
     * Controller上一层相关异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler({
            NoHandlerFoundException.class,
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            HttpMediaTypeNotAcceptableException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            MissingServletRequestPartException.class,
            AsyncRequestTimeoutException.class
    })
    public ModelAndView handleServletException(Exception e, NativeWebRequest request) {
        log.error(getStackTrace(e));
        int code = 9999;

        return this.getResponse(request, code, "服务器内部异常");
    }

    /**
     * 参数绑定异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = BindException.class)
    public ModelAndView handleBindException(BindException e, NativeWebRequest request) {
        log.error(getStackTrace(e));

        return wrapperBindingResult(e.getBindingResult(), request);
    }

    /**
     * 参数校验(Valid)异常，将校验失败的所有异常组合成一条错误信息
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ModelAndView handleValidException(MethodArgumentNotValidException e, NativeWebRequest request) {
        log.error(getStackTrace(e));

        return wrapperBindingResult(e.getBindingResult(), request);
    }

    /**
     * 参数检验异常
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public ModelAndView handleValidException(ConstraintViolationException e, NativeWebRequest request) {
        log.error(getStackTrace(e));

        Set<String> msg = new HashSet<>();
        e.getConstraintViolations().forEach(v -> {
            StringBuilder msgStr = new StringBuilder();
            String[] name = v.getPropertyPath().toString().split("[.]");
            msgStr.append(name[name.length - 1]).append(":").append(v.getMessage());
            msg.add(msgStr.toString());
        });

        return this.getResponse(request, ArgumentErrorInfoEnum.VALID_ERROR.getCode(), String.join(",", msg));
    }

    /**
     * 包装绑定异常结果
     *
     * @param bindingResult 绑定结果
     * @return 异常结果
     */
    private ModelAndView wrapperBindingResult(BindingResult bindingResult, NativeWebRequest request) {
        Set<String> msg = new HashSet<>();
        for (ObjectError error : bindingResult.getAllErrors()) {
            StringBuilder msgStr = new StringBuilder();
            if (error instanceof FieldError) {
                msgStr.append(((FieldError) error).getField()).append(":");
            }
            msgStr.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());
            msg.add(msgStr.toString());
        }

        return this.getResponse(request, ArgumentErrorInfoEnum.VALID_ERROR.getCode(), String.join(",", msg));
    }

    /**
     * 未定义异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = Exception.class)
    public ModelAndView handleException(Exception e, NativeWebRequest request) {
        log.error(getStackTrace(e));

        return this.getResponse(request, CommonErrorInfoEnum.SERVER_ERROR.getCode(), CommonErrorInfoEnum.SERVER_ERROR.getMessage());
    }

    /**
     * 获取响应结果
     * @param request
     * @param code
     * @param message
     * @return
     */
    private ModelAndView getResponse(NativeWebRequest request, int code, String message) {
        String accept = request.getHeader("Accept");
        if(!StringUtils.isEmpty(accept) && accept.contains(MediaType.TEXT_HTML_VALUE)) {
            return new ModelAndView("error");
        } else {
            ModelAndView mav = new ModelAndView(new MappingJackson2JsonView());
            mav.addObject("ret", 1);
            mav.addObject("errorcode", code);
            mav.addObject("msg", message);
            return mav;
        }
    }

    public static String getStackTrace(Throwable t) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        try {
            t.printStackTrace(pw);
            return sw.toString();
        } finally {
            pw.close();
        }
    }
}
